/* See the files LICENSE and COPYRIGHT for license and copyright information */
package org.rubiq.reader;

import java.io.IOException;
import java.io.PushbackReader;

import org.rubiq.LispCons;
import org.rubiq.LispExpression;
import org.rubiq.LispScalar;
import org.rubiq.LispString;
import org.rubiq.LispNil;

/**
 * @author <a href="mailto:ola.bini@gmail.com">Ola Bini</a>
 */
public abstract class ReaderMacro {
    public static void initDefaultMacros(ReaderMacro[] rms) {
        rms['('] = LIST;
        rms['\''] = QUOTE;
        rms['"'] = STRING;
        rms[')'] = LIST_END;
        rms['.'] = DOT;
    }

    public abstract LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException;

    public static ReaderMacro SCALAR = new ReaderMacro() {
            private boolean isEnd(int tag) {
                return Character.isWhitespace(tag) || tag == ')' || tag == ']' || tag == '}';
            }

            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                reader.unread((char)tag);
                StringBuffer sbe = new StringBuffer();
                int val;
                while((val = reader.read()) != -1 && !isEnd(val)) {
                    sbe.append((char)val);
                }
                if(val != -1) {
                    reader.unread((char)val);
                }
                String ss = sbe.toString();
                if(ss.equalsIgnoreCase("nil")) {
                    return new LispNil();
                }
                return LispScalar.createScalar(ss);
            }
        };


    private static LispExpression LIST_END_TOKEN = new LispExpression() {};
    private static LispExpression DOT_TOKEN = new LispExpression() {};

    public static ReaderMacro LIST_END = new ReaderMacro() {
            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                if(!(parent instanceof LispCons) ) {
                    throw new RuntimeException("An object cannot start with #\\)");
                }
                return LIST_END_TOKEN;
            }
        };
    public static ReaderMacro DOT = new ReaderMacro() {
            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                return DOT_TOKEN;
            }
        };

    public static ReaderMacro LIST = new ReaderMacro() {
            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                LispExpression val = lispReader.readNext(new LispCons(null));
                if(val == LIST_END_TOKEN) {
                    return new LispNil();
                }
                LispCons fcons = LispCons.list(val);
                LispCons cons = fcons;
                while((val = lispReader.readNext(fcons)) != LIST_END_TOKEN) {
                    cons = cons.append(val);
                }
                return fcons;
            }
        };

    public static ReaderMacro QUOTE = new ReaderMacro() {
            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                LispCons quoted = LispCons.list(LispScalar.createScalar("QUOTE"));
                LispExpression val = lispReader.readNext(quoted);
                if(val instanceof LispNil) {
                    return val;
                }
                quoted.append(val);
                return quoted;
            }
        };

    public static ReaderMacro STRING = new ReaderMacro() {
            public LispExpression read(DefaultReader lispReader, PushbackReader reader, int tag, LispExpression parent) throws IOException {
                StringBuffer sb = new StringBuffer();
                int vv;
                while((vv = reader.read()) != '"') {
                    switch(vv) {
                    case -1:
                        throw new RuntimeException("Premature end of String");
                    case '\\':
                        vv = reader.read();
                        // FALLTHROUGH
                    default:
                        sb.append((char)vv);
                    }
                }
                return new LispString(sb.toString());
            }
        };
}// ReaderMacro
